home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / sendmail.8.8.4.tar.gz / sendmail.8.8.4.tar / sendmail-8.8.4 / contrib / expn.pl < prev    next >
Perl Script  |  1995-10-28  |  37KB  |  1,368 lines

  1. #!/usr/bin/perl
  2. 'di ';
  3. 'ds 00 \\"';
  4. 'ig 00 ';
  5. #
  6. #       THIS PROGRAM IS ITS OWN MANUAL PAGE.  INSTALL IN man & bin.
  7. #
  8.  
  9. # hardcoded constants, should work fine for BSD-based systems
  10. require 'sys/socket.ph';
  11. $sockaddr = 'S n a4 x8';
  12.  
  13. # system requirements:
  14. #     must have 'nslookup' and 'hostname' programs.
  15.  
  16. # $Header: /home/muir/bin/RCS/expn,v 3.9 1995/10/02 17:51:35 muir Exp muir $
  17.  
  18. # TODO:
  19. #    less magic should apply to command-line addresses
  20. #    less magic should apply to local addresses
  21. #    add magic to deal with cross-domain cnames
  22.  
  23. # Checklist: (hard addresses)
  24. #    250 Kimmo Suominen <"|/usr/local/mh/lib/slocal -user kim"@grendel.tac.nyc.ny.us>
  25. #    harry@hofmann.cs.Berkeley.EDU -> harry@tenet (.berkeley.edu)  [dead]
  26. #    bks@cs.berkeley.edu -> shiva.CS (.berkeley.edu)              [dead]
  27. #    dan@tc.cornell.edu -> brown@tiberius (.tc.cornell.edu)
  28.  
  29. #############################################################################
  30. #
  31. #  Copyright (c) 1993 David Muir Sharnoff
  32. #  All rights reserved.
  33. #
  34. #  Redistribution and use in source and binary forms, with or without
  35. #  modification, are permitted provided that the following conditions
  36. #  are met:
  37. #  1. Redistributions of source code must retain the above copyright
  38. #     notice, this list of conditions and the following disclaimer.
  39. #  2. Redistributions in binary form must reproduce the above copyright
  40. #     notice, this list of conditions and the following disclaimer in the
  41. #     documentation and/or other materials provided with the distribution.
  42. #  3. All advertising materials mentioning features or use of this software
  43. #     must display the following acknowledgement:
  44. #       This product includes software developed by the David Muir Sharnoff.
  45. #  4. The name of David Sharnoff may not be used to endorse or promote products
  46. #     derived from this software without specific prior written permission.
  47. #
  48. #  THIS SOFTWARE IS PROVIDED BY THE DAVID MUIR SHARNOFF ``AS IS'' AND
  49. #  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  50. #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  51. #  ARE DISCLAIMED.  IN NO EVENT SHALL DAVID MUIR SHARNOFF BE LIABLE
  52. #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  53. #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  54. #  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  55. #  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  56. #  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  57. #  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  58. #  SUCH DAMAGE.
  59. #
  60. # This copyright notice derrived from material copyrighted by the Regents
  61. # of the University of California.
  62. #
  63. # Contributions accepted.
  64. #
  65. #############################################################################
  66.  
  67. # overall structure:
  68. #    in an effort to not trace each address individually, but rather
  69. #    ask each server in turn a whole bunch of questions, addresses to
  70. #    be expanded are queued up.
  71. #
  72. #    This means that all accounting w.r.t. an address must be stored in
  73. #    various arrays.  Generally these arrays are indexed by the
  74. #    string "$addr *** $server" where $addr is the address to be
  75. #    expanded "foo" or maybe "foo@bar" and $server is the hostname
  76. #    of the SMTP server to contact.
  77. #
  78.  
  79. # important global variables:
  80. #
  81. # @hosts : list of servers still to be contacted
  82. # $server : name of the current we are currently looking at
  83. # @users = $users{@hosts[0]} : addresses to expand at this server
  84. # $u = $users[0] : the current address being expanded
  85. # $names{"$users[0] *** $server"} : the 'name' associated with the address
  86. # $mxbacktrace{"$users[0] *** $server"} : record of mx expansion
  87. # $mx_secondary{$server} : other mx relays at the same priority
  88. # $domainify_fallback{"$users[0] *** $server"} : alternative names to try 
  89. #    instead of $server if $server doesn't work
  90. # $temporary_redirect{"$users[0] *** $server"} : when trying alternates,
  91. #    temporarily channel all tries along current path
  92. # $giveup{$server} : do not bother expanding addresses at $server
  93. # $verbose : -v
  94. # $watch : -w
  95. # $vw : -v or -w
  96. # $debug : -d
  97. # $valid : -a
  98. # $levels : -1
  99. # S : the socket connection to $server
  100.  
  101. $have_nslookup = 1;    # we have the nslookup program
  102. $port = 'smtp';
  103. $av0 = $0;
  104. $ENV{'PATH'} .= ":/usr/etc" unless $ENV{'PATH'} =~ m,/usr/etc,;
  105. $ENV{'PATH'} .= ":/usr/ucb" unless $ENV{'PATH'} =~ m,/usr/ucb,;
  106. select(STDERR);
  107.  
  108. $0 = "$av0 - running hostname";
  109. chop($name = `hostname || uname -n`);
  110.  
  111. $0 = "$av0 - lookup host FQDN and IP addr";
  112. ($hostname,$aliases,$type,$len,$thisaddr) = gethostbyname($name);
  113.  
  114. $0 = "$av0 - parsing args";
  115. $usage = "Usage: $av0 [-1avwd] user[\@host] [user2[host2] ...]";
  116. for $a (@ARGV) {
  117.     die $usage if $a eq "-";
  118.     while ($a =~ s/^(-.*)([1avwd])/$1/) {
  119.         eval '$'."flag_$2 += 1";
  120.     }
  121.     next if $a eq "-";
  122.     die $usage if $a =~ /^-/;
  123.     &expn(&parse($a,$hostname,undef,1));
  124. }
  125. $verbose = $flag_v;
  126. $watch = $flag_w;
  127. $vw = $flag_v + $flag_w;
  128. $debug = $flag_d;
  129. $valid = $flag_a;
  130. $levels = $flag_1;
  131.  
  132. die $usage unless @hosts;
  133. if ($valid) {
  134.     if ($valid == 1) {
  135.         $validRequirement = 0.8;
  136.     } elsif ($valid == 2) {
  137.         $validRequirement = 1.0;
  138.     } elsif ($valid == 3) {
  139.         $validRequirement = 0.9;
  140.     } else {
  141.         $validRequirement = (1 - (1/($valid-3)));
  142.         print "validRequirement = $validRequirement\n" if $debug;
  143.     }
  144. }
  145.  
  146. $0 = "$av0 - building local socket";
  147. ($name,$aliases,$proto) = getprotobyname('tcp');
  148. ($name,$aliases,$port) = getservbyname($port,'tcp')
  149.     unless $port =~ /^\d+/;
  150. $this = pack($sockaddr, &AF_INET, 0, $thisaddr);
  151.  
  152. HOST:
  153. while (@hosts) {
  154.     $server = shift(@hosts);
  155.     @users = split(' ',$users{$server});
  156.     delete $users{$server};
  157.  
  158.     # is this server already known to be bad?
  159.     $0 = "$av0 - looking up $server";
  160.     if ($giveup{$server}) {
  161.         &giveup('mx domainify',$giveup{$server});
  162.         next;
  163.     }
  164.  
  165.     # do we already have an mx record for this host?
  166.     next HOST if &mxredirect($server,*users);
  167.  
  168.     # look it up, or try for an mx.
  169.     $0 = "$av0 - gethostbyname($server)";
  170.  
  171.     ($name,$aliases,$type,$len,$thataddr) = gethostbyname($server);
  172.     # if we can't get an A record, try for an MX record.
  173.     unless($thataddr) {
  174.         &mxlookup(1,$server,"$server: could not resolve name",*users);
  175.         next HOST;
  176.     }
  177.                 
  178.     # get a connection, or look for an mx
  179.     $0 = "$av0 - socket to $server";
  180.     $that = pack($sockaddr, &AF_INET, $port, $thataddr);
  181.     socket(S, &AF_INET, &SOCK_STREAM, $proto)
  182.         || die "socket: $!";
  183.     $0 = "$av0 - bind to $server";
  184.     bind(S, $this) 
  185.         || die "bind $hostname,0: $!";
  186.     $0 = "$av0 - connect to $server";
  187.     print "debug = $debug server = $server\n" if $debug > 8;
  188.     if (! connect(S, $that) || ($debug == 10 && $server =~ /relay\d.UU.NET$/i)) {
  189.         $0 = "$av0 - $server: could not connect: $!\n";
  190.         $emsg = $!;
  191.         unless (&mxlookup(0,$server,"$server: could not connect: $!",*users)) {
  192.             &giveup('mx',"$server: Could not connect: $emsg");
  193.         }
  194.         next HOST;
  195.     }
  196.     select((select(S),$| = 1)[0]); # don't buffer output to S
  197.  
  198.     # read the greeting
  199.     $0 = "$av0 - talking to $server";
  200.     &alarm("greeting with $server",'');
  201.     while(<S>) {
  202.         alarm(0);
  203.         print if $watch;
  204.         if (/^(\d+)([- ])/) {
  205.             if ($1 != 220) {
  206.                 $0 = "$av0 - bad numeric response from $server";
  207.                 &alarm("giving up after bad response from $server",'');
  208.                 &read_response($2,$watch);
  209.                 alarm(0);
  210.                 print STDERR "$server: NOT 220 greeting: $_"
  211.                     if ($debug || $vw);
  212.                 if (&mxlookup(0,$server,"$server: did not respond with a 220 greeting",*users)) {
  213.                     close(S);
  214.                     next HOST;
  215.                 }
  216.             }
  217.             last if ($2 eq " ");
  218.         } else {
  219.             $0 = "$av0 - bad response from $server";
  220.             print STDERR "$server: NOT 220 greeting: $_"
  221.                 if ($debug || $vw);
  222.             unless (&mxlookup(0,$server,"$server: did not respond with SMTP codes",*users)) {
  223.                 &giveup('',"$server: did not talk SMTP");
  224.             }
  225.             close(S);
  226.             next HOST;
  227.         }
  228.         &alarm("greeting with $server",'');
  229.     }
  230.     alarm(0);
  231.     
  232.     # if this causes problems, remove it
  233.     $0 = "$av0 - sending helo to $server";
  234.     &alarm("sending helo to $server","");
  235.     &ps("helo $hostname");
  236.     while(<S>) {
  237.         print if $watch;
  238.         last if /^\d+ /;
  239.     }
  240.     alarm(0);
  241.  
  242.     # try the users, one by one
  243.     USER:
  244.     while(@users) {
  245.         $u = shift(@users);
  246.         $0 = "$av0 - expanding $u [\@$server]";
  247.  
  248.         # do we already have a name for this user?
  249.         $oldname = $names{"$u *** $server"};
  250.  
  251.         print &compact($u,$server)." ->\n" if ($verbose && ! $valid);
  252.         if ($valid) {
  253.             #
  254.             # when running with -a, we delay taking any action 
  255.             # on the results of our query until we have looked
  256.             # at the complete output.  @toFinal stores expansions
  257.             # that will be final if we take them.  @toExpn stores
  258.             # expnansions that are not final.  @isValid keeps
  259.             # track of our ability to send mail to each of the
  260.             # expansions.
  261.             #
  262.             @isValid = ();
  263.             @toFinal = ();
  264.             @toExpn = ();
  265.         }
  266.  
  267. #        ($ecode,@expansion) = &expn_vrfy($u,$server);
  268.         (@foo) = &expn_vrfy($u,$server);
  269.         ($ecode,@expansion) = @foo;
  270.         if ($ecode) {
  271.             &giveup('',$ecode,$u);
  272.             last USER;
  273.         }
  274.  
  275.         for $s (@expansion) {
  276.             $s =~ s/[\n\r]//g;
  277.             $0 = "$av0 - parsing $server: $s";
  278.  
  279.             $skipwatch = $watch;
  280.  
  281.             if ($s =~ /^[25]51([- ]).*<(.+)>/) {
  282.                 print "$s" if $watch;
  283.                 print "(pretending 250$1<$2>)" if ($debug && $watch);
  284.                 print "\n" if $watch;
  285.                 $s = "250$1<$2>";
  286.                 $skipwatch = 0;
  287.             }
  288.  
  289.             if ($s =~ /^250([- ])(.+)/) {
  290.                 print "$s\n" if $skipwatch;
  291.                 ($done,$addr) = ($1,$2);
  292.                 ($newhost, $newaddr, $newname) =  &parse($addr,$server,$oldname, $#expansion == 0);
  293.                 print "($newhost, $newaddr, $newname) = &parse($addr, $server, $oldname)\n" if $debug;
  294.                 if (! $newhost) {
  295.                     # no expansion is possible w/o a new server to call
  296.                     if ($valid) {
  297.                         push(@isValid, &validAddr($newaddr));
  298.                         push(@toFinal,$newaddr,$server,$newname);
  299.                     } else {
  300.                         &verbose(&final($newaddr,$server,$newname));
  301.                     }
  302.                 } else {
  303.                     $newmxhost = &mx($newhost,$newaddr);
  304.                     print "$newmxhost = &mx($newhost)\n" 
  305.                         if ($debug && $newhost ne $newmxhost);
  306.                     $0 = "$av0 - parsing $newaddr [@$newmxhost]";
  307.                     print "levels = $levels, level{$u *** $server} = ".$level{"$u *** $server"}."\n" if ($debug > 1);
  308.                     # If the new server is the current one, 
  309.                     # it would have expanded things for us
  310.                     # if it could have.  Mx records must be
  311.                     # followed to compare server names.
  312.                     # We are also done if the recursion
  313.                     # count has been exceeded.
  314.                     if (&trhost($newmxhost) eq &trhost($server) || ($levels && $level{"$u *** $server"} >= $levels)) {
  315.                         if ($valid) {
  316.                             push(@isValid, &validAddr($newaddr));
  317.                             push(@toFinal,$newaddr,$newmxhost,$newname);
  318.                         } else {
  319.                             &verbose(&final($newaddr,$newmxhost,$newname));
  320.                         }
  321.                     } else {
  322.                         # more work to do...
  323.                         if ($valid) {
  324.                             push(@isValid, &validAddr($newaddr));
  325.                             push(@toExpn,$newmxhost,$newaddr,$newname,$level{"$u *** $server"});
  326.                         } else {
  327.                             &verbose(&expn($newmxhost,$newaddr,$newname,$level{"$u *** $server"}));
  328.                         }
  329.                     }
  330.                 }
  331.                 last if ($done eq " ");
  332.                 next;
  333.             }
  334.             # 550 is a known code...  Should the be
  335.             # included in -a output?  Might be a bug
  336.             # here.  Does it matter?  Can assume that
  337.             # there won't be UNKNOWN USER responses 
  338.             # mixed with valid users?
  339.             if ($s =~ /^(550)([- ])/) {
  340.                 if ($valid) {
  341.                     print STDERR "\@$server:$u ($oldname) USER UNKNOWN\n";
  342.                 } else {
  343.                     &verbose(&final($u,$server,$oldname,"USER UNKNOWN"));
  344.                 }
  345.                 last if ($2 eq " ");
  346.                 next;
  347.             } 
  348.             # 553 is a known code...  
  349.             if ($s =~ /^(553)([- ])/) {
  350.                 if ($valid) {
  351.                     print STDERR "\@$server:$u ($oldname) USER AMBIGUOUS\n";
  352.                 } else {
  353.                     &verbose(&final($u,$server,$oldname,"USER AMBIGUOUS"));
  354.                 }
  355.                 last if ($2 eq " ");
  356.                 next;
  357.             } 
  358.             # 252 is a known code...  
  359.             if ($s =~ /^(252)([- ])/) {
  360.                 if ($valid) {
  361.                     print STDERR "\@$server:$u ($oldname) REFUSED TO VRFY\n";
  362.                 } else {
  363.                     &verbose(&final($u,$server,$oldname,"REFUSED TO VRFY"));
  364.                 }
  365.                 last if ($2 eq " ");
  366.                 next;
  367.             } 
  368.             &giveup('',"$server: did not grok '$s'",$u);
  369.             last USER;
  370.         }
  371.  
  372.         if ($valid) {
  373.             #
  374.             # now we decide if we are going to take these
  375.             # expansions or roll them back.
  376.             #
  377.             $avgValid = &average(@isValid);
  378.             print "avgValid = $avgValid\n" if $debug;
  379.             if ($avgValid >= $validRequirement) {
  380.                 print &compact($u,$server)." ->\n" if $verbose;
  381.                 while (@toExpn) {
  382.                     &verbose(&expn(splice(@toExpn,0,4)));
  383.                 }
  384.                 while (@toFinal) {
  385.                     &verbose(&final(splice(@toFinal,0,3)));
  386.                 }
  387.             } else {
  388.                 print "Tossing some valid to avoid invalid ".&compact($u,$server)."\n" if ($avgValid > 0.0 && ($vw || $debug));
  389.                 print &compact($u,$server)." ->\n" if $verbose;
  390.                 &verbose(&final($u,$server,$newname));
  391.             }
  392.         }
  393.     }
  394.  
  395.     &alarm("sending 'quit' to $server",'');
  396.     $0 = "$av0 - sending 'quit' to $server";
  397.     &ps("quit");
  398.     while(<S>) {
  399.         print if $watch;
  400.         last if /^\d+ /;
  401.     }
  402.     close(S);
  403.     alarm(0);
  404. }
  405.  
  406. $0 = "$av0 - printing final results";
  407. print "----------\n" if $vw;
  408. select(STDOUT);
  409. for $f (sort @final) {
  410.     print "$f\n";
  411. }
  412. unlink("/tmp/expn$$");
  413. exit(0);
  414.  
  415.  
  416. # abandon all attempts deliver to $server
  417. # register the current addresses as the final ones
  418. sub giveup
  419. {
  420.     local($redirect_okay,$reason,$user) = @_;
  421.     local($us,@so,$nh,@remaining_users);
  422.     local($pk,$file,$line);
  423.     ($pk, $file, $line) = caller;
  424.  
  425.     $0 = "$av0 - giving up on $server: $reason";
  426.     #
  427.     # add back a user if we gave up in the middle
  428.     #
  429.     push(@users,$user) if $user;
  430.     #
  431.     # don't bother with this system anymore
  432.     #
  433.     unless ($giveup{$server}) {
  434.         $giveup{$server} = $reason;
  435.         print STDERR "$reason\n";
  436.     }
  437.     print "Giveup at $file:$line!!! redirect okay = $redirect_okay; $reason\n" if $debug;
  438.     #
  439.     # Wait!
  440.     # Before giving up, see if there is a chance that
  441.     # there is another host to redirect to!
  442.     # (Kids, don't do this at home!  Hacking is a dangerous
  443.     # crime and you could end up behind bars.)
  444.     #
  445.     for $u (@users) {
  446.         if ($redirect_okay =~ /\bmx\b/) {
  447.             next if &try_fallback('mx',$u,*server,
  448.                 *mx_secondary,
  449.                 *already_mx_fellback);
  450.         }
  451.         if ($redirect_okay =~ /\bdomainify\b/) {
  452.             next if &try_fallback('domainify',$u,*server,
  453.                 *domainify_fallback,
  454.                 *already_domainify_fellback);
  455.         }
  456.         push(@remaining_users,$u);
  457.     }
  458.     @users = @remaining_users;
  459.     for $u (@users) {
  460.         print &compact($u,$server)." ->\n" if ($verbose && $valid && $u);
  461.         &verbose(&final($u,$server,$names{"$u *** $server"},$reason));
  462.     }
  463. }
  464. #
  465. # This routine is used only within &giveup.  It checks to
  466. # see if we really have to giveup or if there is a second
  467. # chance because we did something before that can be 
  468. # backtracked.
  469. #
  470. # %fallback{"$user *** $host"} tracks what is able to fallback
  471. # %fellback{"$user *** $host"} tracks what has fallen back
  472. #
  473. # If there is a valid backtrack, then queue up the new possibility
  474. #
  475. sub try_fallback
  476. {
  477.     local($method,$user,*host,*fall_table,*fellback) = @_;
  478.     local($us,$fallhost,$oldhost,$ft,$i);
  479.  
  480.     if ($debug > 8) {
  481.         print "Fallback table $method:\n";
  482.         for $i (sort keys %fall_table) {
  483.             print "\t'$i'\t\t'$fall_table{$i}'\n";
  484.         }
  485.         print "Fellback table $method:\n";
  486.         for $i (sort keys %fellback) {
  487.             print "\t'$i'\t\t'$fellback{$i}'\n";
  488.         }
  489.         print "U: $user H: $host\n";
  490.     }
  491.     
  492.     $us = "$user *** $host";
  493.     if (defined $fellback{$us}) {
  494.         #
  495.         # Undo a previous fallback so that we can try again
  496.         # Nested fallbacks are avoided because they could
  497.         # lead to infinite loops
  498.         #
  499.         $fallhost = $fellback{$us};
  500.         print "Already $method fell back from $us -> \n" if $debug;
  501.         $us = "$user *** $fallhost";
  502.         $oldhost = $fallhost;
  503.     } elsif (($method eq 'mx') && (defined $mxbacktrace{$us}) && (defined $mx_secondary{$mxbacktrace{$us}})) {
  504.         print "Fallback an MX expansion $us -> \n" if $debug;
  505.         $oldhost = $mxbacktrace{$us};
  506.     } else {
  507.         print "Oldhost($host, $us) = " if $debug;
  508.         $oldhost = $host;
  509.     }
  510.     print "$oldhost\n" if $debug;
  511.     if (((defined $fall_table{$us}) && ($ft = $us)) || ((defined $fall_table{$oldhost}) && ($ft = $oldhost))) {
  512.         print "$method Fallback = ".$fall_table{$ft}."\n" if $debug;
  513.         local(@so,$newhost);
  514.         @so = split(' ',$fall_table{$ft});
  515.         $newhost = shift(@so);
  516.         print "Falling back ($method) $us -> $newhost (from $oldhost)\n" if $debug;
  517.         if ($method eq 'mx') {
  518.             if (! defined ($mxbacktrace{"$user *** $newhost"})) {
  519.                 if (defined $mxbacktrace{"$user *** $oldhost"}) {
  520.                     print "resetting oldhost $oldhost to the original: " if $debug;
  521.                     $oldhost = $mxbacktrace{"$user *** $oldhost"};
  522.                     print "$oldhost\n" if $debug;
  523.                 }
  524.                 $mxbacktrace{"$user *** $newhost"} = $oldhost;
  525.                 print "mxbacktrace $user *** $newhost -> $oldhost\n" if $debug;
  526.             }
  527.             $mx{&trhost($oldhost)} = $newhost;
  528.         } else {
  529.             $temporary_redirect{$us} = $newhost;
  530.         }
  531.         if (@so) {
  532.             print "Can still $method  $us: @so\n" if $debug;
  533.             $fall_table{$ft} = join(' ',@so);
  534.         } else {
  535.             print "No more fallbacks for $us\n" if $debug;
  536.             delete $fall_table{$ft};
  537.         }
  538.         if (defined $create_host_backtrack{$us}) {
  539.             $create_host_backtrack{"$user *** $newhost"} 
  540.                 = $create_host_backtrack{$us};
  541.         }
  542.         $fellback{"$user *** $newhost"} = $oldhost;
  543.         &expn($newhost,$user,$names{$us},$level{$us});
  544.         return 1;
  545.     }
  546.     delete $temporary_redirect{$us};
  547.     $host = $oldhost;
  548.     return 0;
  549. }
  550. # return 1 if you could send mail to the address as is.
  551. sub validAddr
  552. {
  553.     local($addr) = @_;
  554.     $res = &do_validAddr($addr);
  555.     print "validAddr($addr) = $res\n" if $debug;
  556.     $res;
  557. }
  558. sub do_validAddr
  559. {
  560.     local($addr) = @_;
  561.     local($urx) = "[-A-Za-z_.0-9+]+";
  562.  
  563.     # \u
  564.     return 0 if ($addr =~ /^\\/);
  565.     # ?@h
  566.     return 1 if ($addr =~ /.\@$urx$/);
  567.     # @h:?
  568.     return 1 if ($addr =~ /^\@$urx\:./);
  569.     # h!u
  570.     return 1 if ($addr =~ /^$urx!./);
  571.     # u
  572.     return 1 if ($addr =~ /^$urx$/);
  573.     # ?
  574.     print "validAddr($addr) = ???\n" if $debug;
  575.     return 0;
  576. }
  577. # Some systems use expn and vrfy interchangeably.  Some only
  578. # implement one or the other.  Some check expn against mailing
  579. # lists and vrfy against users.  It doesn't appear to be
  580. # consistent.
  581. #
  582. # So, what do we do?  We try everything!
  583. #
  584. #
  585. # Ranking of result codes: good: 250, 251/551, 252, 550, anything else
  586. #
  587. # Ranking of inputs: best: user@host.domain, okay: user
  588. #
  589. # Return value: $error_string, @responses_from_server
  590. sub expn_vrfy
  591. {
  592.     local($u,$server) = @_;
  593.     local(@c) = ('expn', 'vrfy');
  594.     local(@try_u) = $u;
  595.     local(@ret,$code);
  596.  
  597.     if (($u =~ /(.+)@(.+)/) && (&trhost($2) eq &trhost($server))) {
  598.         push(@try_u,$1);
  599.     }
  600.  
  601.     TRY:
  602.     for $c (@c) {
  603.         for $try_u (@try_u) {
  604.             &alarm("${c}'ing $try_u on $server",'',$u);
  605.             &ps("$c $try_u");
  606.             alarm(0);
  607.             $s = <S>;
  608.             if ($s eq '') {
  609.                 return "$server: lost connection";
  610.             }
  611.             if ($s !~ /^(\d+)([- ])/) {
  612.                 return "$server: garbled reply to '$c $try_u'";
  613.             }
  614.             if ($1 == 250) {
  615.                 $code = 250;
  616.                 @ret = ("",$s);
  617.                 push(@ret,&read_response($2,$debug));
  618.                 return (@ret);
  619.             } 
  620.             if ($1 == 551 || $1 == 251) {
  621.                 $code = $1;
  622.                 @ret = ("",$s);
  623.                 push(@ret,&read_response($2,$debug));
  624.                 next;
  625.             }
  626.             if ($1 == 252 && ($code == 0 || $code == 550)) {
  627.                 $code = 252;
  628.                 @ret = ("",$s);
  629.                 push(@ret,&read_response($2,$watch));
  630.                 next;
  631.             }
  632.             if ($1 == 550 && $code == 0) {
  633.                 $code = 550;
  634.                 @ret = ("",$s);
  635.                 push(@ret,&read_response($2,$watch));
  636.                 next;
  637.             }
  638.             &read_response($2,$watch);
  639.         }
  640.     }
  641.     return "$server: expn/vrfy not implemented" unless @ret;
  642.     return @ret;
  643. }
  644. # sometimes the old parse routine (now parse2) didn't
  645. # reject funky addresses. 
  646. sub parse
  647. {
  648.     local($oldaddr,$server,$oldname,$one_to_one) = @_;
  649.     local($newhost, $newaddr, $newname, $um) =  &parse2($oldaddr,$server,$oldname,$one_to_one);
  650.     if ($newaddr =~ m,^["/],) {
  651.         return (undef, $oldaddr, $newname) if $valid;
  652.         return (undef, $um, $newname);
  653.     }
  654.     return ($newhost, $newaddr, $newname);
  655. }
  656.  
  657. # returns ($new_smtp_server,$new_address,$new_name)
  658. # given a response from a SMTP server ($newaddr), the 
  659. # current host ($server), the old "name" and a flag that
  660. # indicates if it is being called during the initial 
  661. # command line parsing ($parsing_args)
  662. sub parse2
  663. {
  664.     local($newaddr,$context_host,$old_name,$parsing_args) = @_;
  665.     local(@names) = $old_name;
  666.     local($urx) = "[-A-Za-z_.0-9+]+";
  667.     local($unmangle);
  668.  
  669.     #
  670.     # first, separate out the address part.
  671.     #
  672.  
  673.     #
  674.     # [NAME] <ADDR [(NAME)]>
  675.     # [NAME] <[(NAME)] ADDR
  676.     # ADDR [(NAME)]
  677.     # (NAME) ADDR
  678.     # [(NAME)] <ADDR>
  679.     #
  680.     if ($newaddr =~ /^\<(.*)\>$/) {
  681.         print "<A:$1>\n" if $debug;
  682.         ($newaddr) = &trim($1);
  683.         print "na = $newaddr\n" if $debug;
  684.     }
  685.     if ($newaddr =~ /^([^\<\>]*)\<([^\<\>]*)\>([^\<\>]*)$/) {
  686.         # address has a < > pair in it.
  687.         print "N:$1 <A:$2> N:$3\n" if $debug;
  688.         ($newaddr) = &trim($2);
  689.         unshift(@names, &trim($3,$1));
  690.         print "na = $newaddr\n" if $debug;
  691.     }
  692.     if ($newaddr =~ /^([^\(\)]*)\(([^\(\)]*)\)([^\(\)]*)$/) {
  693.         # address has a ( ) pair in it.
  694.         print "A:$1 (N:$2) A:$3\n" if $debug;
  695.         unshift(@names,&trim($2));
  696.         local($f,$l) = (&trim($1),&trim($3));
  697.         if (($f && $l) || !($f || $l)) {
  698.             # address looks like:
  699.             # foo (bar) baz  or (bar)
  700.             # not allowed!
  701.             print STDERR "Could not parse $newaddr\n" if $vw;
  702.             return(undef,$newaddr,&firstname(@names));
  703.         }
  704.         $newaddr = $f if $f;
  705.         $newaddr = $l if $l;
  706.         print "newaddr now = $newaddr\n" if $debug;
  707.     }
  708.     #
  709.     # @foo:bar
  710.     # j%k@l
  711.     # a@b
  712.     # b!a
  713.     # a
  714.     #
  715.     $unmangle = $newaddr;
  716.     if ($newaddr =~ /^\@($urx)\:(.+)$/) {
  717.         print "(\@:)" if $debug;
  718.         # this is a bit of a cheat, but it seems necessary
  719.         return (&domainify($1,$context_host,$2),$2,&firstname(@names),$unmangle);
  720.     }
  721.     if ($newaddr =~ /^(.+)\@($urx)$/) {
  722.         print "(\@)" if $debug;
  723.         return (&domainify($2,$context_host,$newaddr),$newaddr,&firstname(@names),$unmangle);
  724.     }
  725.     if ($parsing_args) {
  726.         if ($newaddr =~ /^($urx)\!(.+)$/) {
  727.             return (&domainify($1,$context_host,$newaddr),$newaddr,&firstname(@names),$unmangle);
  728.         }
  729.         if ($newaddr =~ /^($urx)$/) {
  730.             return ($context_host,$newaddr,&firstname(@names),$unmangle);
  731.         }
  732.         print STDERR "Could not parse $newaddr\n";
  733.     }
  734.     print "(?)" if $debug;
  735.     return(undef,$newaddr,&firstname(@names),$unmangle);
  736. }
  737. # return $u (@$server) unless $u includes reference to $server
  738. sub compact
  739. {
  740.     local($u, $server) = @_;
  741.     local($se) = $server;
  742.     local($sp);
  743.     $se =~ s/(\W)/\\$1/g;
  744.     $sp = " (\@$server)";
  745.     if ($u !~ /$se/i) {
  746.         return "$u$sp";
  747.     }
  748.     return $u;
  749. }
  750. # remove empty (spaces don't count) members from an array
  751. sub trim
  752. {
  753.     local(@v) = @_;
  754.     local($v,@r);
  755.     for $v (@v) {
  756.         $v =~ s/^\s+//;
  757.         $v =~ s/\s+$//;
  758.         push(@r,$v) if ($v =~ /\S/);
  759.     }
  760.     return(@r);
  761. }
  762. # using the host part of an address, and the server name, add the
  763. # servers' domain to the address if it doesn't already have a 
  764. # domain.  Since this sometimes fails, save a back reference so
  765. # it can be unrolled.
  766. sub domainify
  767. {
  768.     local($host,$domain_host,$u) = @_;
  769.     local($domain,$newhost);
  770.  
  771.     # cut of trailing dots 
  772.     $host =~ s/\.$//;
  773.     $domain_host =~ s/\.$//;
  774.  
  775.     if ($domain_host !~ /\./) {
  776.         #
  777.         # domain host isn't, keep $host whatever it is
  778.         #
  779.         print "domainify($host,$domain_host) = $host\n" if $debug;
  780.         return $host;
  781.     }
  782.  
  783.     # 
  784.     # There are several weird situtations that need to be 
  785.     # accounted for.  They have to do with domain relay hosts.
  786.     #
  787.     # Examples: 
  788.     #    host        server        "right answer"
  789.     #    
  790.     #    shiva.cs    cs.berkeley.edu    shiva.cs.berkeley.edu
  791.     #    shiva        cs.berkeley.edu    shiva.cs.berekley.edu
  792.     #    cumulus        reed.edu    @reed.edu:cumulus.uucp
  793.     #     tiberius    tc.cornell.edu    tiberius.tc.cornell.edu
  794.     #
  795.     # The first try must always be to cut the domain part out of 
  796.     # the server and tack it onto the host.
  797.     #
  798.     # A reasonable second try is to tack the whole server part onto
  799.     # the host and for each possible repeated element, eliminate 
  800.     # just that part.
  801.     #
  802.     # These extra "guesses" get put into the %domainify_fallback
  803.     # array.  They will be used to give addresses a second chance
  804.     # in the &giveup routine
  805.     #
  806.  
  807.     local(%fallback);
  808.  
  809.     local($long); 
  810.     $long = "$host $domain_host";
  811.     $long =~ tr/A-Z/a-z/;
  812.     print "long = $long\n" if $debug;
  813.     if ($long =~ s/^([^ ]+\.)([^ ]+) \2(\.[^ ]+\.[^ ]+)/$1$2$3/) {
  814.         # matches shiva.cs cs.berkeley.edu and returns shiva.cs.berkeley.edu
  815.         print "condensed fallback $host $domain_host -> $long\n" if $debug;
  816.         $fallback{$long} = 9;
  817.     }
  818.  
  819.     local($fh);
  820.     $fh = $domain_host;
  821.     while ($fh =~ /\./) {
  822.         print "FALLBACK $host.$fh = 1\n" if $debug > 7;
  823.         $fallback{"$host.$fh"} = 1;
  824.         $fh =~ s/^[^\.]+\.//;
  825.     }
  826.  
  827.     $fallback{"$host.$domain_host"} = 2;
  828.  
  829.     ($domain = $domain_host) =~ s/^[^\.]+//;
  830.     $fallback{"$host$domain"} = 6
  831.         if ($domain =~ /\./);
  832.  
  833.     if ($host =~ /\./) {
  834.         #
  835.         # Host is already okay, but let's look for multiple
  836.         # interpretations
  837.         #
  838.         print "domainify($host,$domain_host) = $host\n" if $debug;
  839.         delete $fallback{$host};
  840.         $domainify_fallback{"$u *** $host"} = join(' ',sort {$fallback{$b} <=> $fallback{$a};} keys %fallback) if %fallback;
  841.         return $host;
  842.     }
  843.  
  844.     $domain = ".$domain_host"
  845.         if ($domain !~ /\..*\./);
  846.     $newhost = "$host$domain";
  847.  
  848.     $create_host_backtrack{"$u *** $newhost"} = $domain_host;
  849.     print "domainify($host,$domain_host) = $newhost\n" if $debug;
  850.     delete $fallback{$newhost};
  851.     $domainify_fallback{"$u *** $newhost"} = join(' ',sort {$fallback{$b} <=> $fallback{$a};} keys %fallback) if %fallback;
  852.     if ($debug) {
  853.         print "fallback = ";
  854.         print $domainify_fallback{"$u *** $newhost"} 
  855.             if defined($domainify_fallback{"$u *** $newhost"});
  856.         print "\n";
  857.     }
  858.     return $newhost;
  859. }
  860. # return the first non-empty element of an array
  861. sub firstname
  862. {
  863.     local(@names) = @_;
  864.     local($n);
  865.     while(@names) {
  866.         $n = shift(@names);
  867.         return $n if $n =~ /\S/;
  868.     }
  869.     return undef;
  870. }
  871. # queue up more addresses to expand
  872. sub expn
  873. {
  874.     local($host,$addr,$name,$level) = @_;
  875.     if ($host) {
  876.         $host = &trhost($host);
  877.  
  878.         if (($debug > 3) || (defined $giveup{$host})) {
  879.             unshift(@hosts,$host) unless $users{$host};
  880.         } else {
  881.             push(@hosts,$host) unless $users{$host};
  882.         }
  883.         $users{$host} .= " $addr";
  884.         $names{"$addr *** $host"} = $name;
  885.         $level{"$addr *** $host"} = $level + 1;
  886.         print "expn($host,$addr,$name)\n" if $debug;
  887.         return "\t$addr\n";
  888.     } else {
  889.         return &final($addr,'NONE',$name);
  890.     }
  891. }
  892. # compute the numerical average value of an array
  893. sub average
  894. {
  895.     local(@e) = @_;
  896.     return 0 unless @e;
  897.     local($e,$sum);
  898.     for $e (@e) {
  899.         $sum += $e;
  900.     }
  901.     $sum / @e;
  902. }
  903. # print to the server (also to stdout, if -w)
  904. sub ps
  905. {
  906.     local($p) = @_;
  907.     print ">>> $p\n" if $watch;
  908.     print S "$p\n";
  909. }
  910. # return case-adjusted name for a host (for comparison purposes)
  911. sub trhost 
  912. {
  913.     # treat foo.bar as an alias for Foo.BAR
  914.     local($host) = @_;
  915.     local($trhost) = $host;
  916.     $trhost =~ tr/A-Z/a-z/;
  917.     if ($trhost{$trhost}) {
  918.         $host = $trhost{$trhost};
  919.     } else {
  920.         $trhost{$trhost} = $host;
  921.     }
  922.     $trhost{$trhost};
  923. }
  924. # re-queue users if an mx record dictates a redirect
  925. # don't allow a user to be redirected more than once
  926. sub mxredirect
  927. {
  928.     local($server,*users) = @_;
  929.     local($u,$nserver,@still_there);
  930.  
  931.     $nserver = &mx($server);
  932.  
  933.     if (&trhost($nserver) ne &trhost($server)) {
  934.         $0 = "$av0 - mx redirect $server -> $nserver\n";
  935.         for $u (@users) {
  936.             if (defined $mxbacktrace{"$u *** $nserver"}) {
  937.                 push(@still_there,$u);
  938.             } else {
  939.                 $mxbacktrace{"$u *** $nserver"} = $server;
  940.                 print "mxbacktrace{$u *** $nserver} = $server\n"
  941.                     if ($debug > 1);
  942.                 &expn($nserver,$u,$names{"$u *** $server"});
  943.             }
  944.         }
  945.         @users = @still_there;
  946.         if (! @users) {
  947.             return $nserver;
  948.         } else {
  949.             return undef;
  950.         }
  951.     }
  952.     return undef;
  953. }
  954. # follow mx records, return a hostname
  955. # also follow temporary redirections comming from &domainify and
  956. # &mxlookup
  957. sub mx
  958. {
  959.     local($h,$u) = @_;
  960.  
  961.     for (;;) {
  962.         if (defined $mx{&trhost($h)} && $h ne $mx{&trhost($h)}) {
  963.             $0 = "$av0 - mx expand $h";
  964.             $h = $mx{&trhost($h)};
  965.             return $h;
  966.         }
  967.         if ($u) {
  968.             if (defined $temporary_redirect{"$u *** $h"}) {
  969.                 $0 = "$av0 - internal redirect $h";
  970.                 print "Temporary redirect taken $u *** $h -> " if $debug;
  971.                 $h = $temporary_redirect{"$u *** $h"};
  972.                 print "$h\n" if $debug;
  973.                 next;
  974.             }
  975.             $htr = &trhost($h);
  976.             if (defined $temporary_redirect{"$u *** $htr"}) {
  977.                 $0 = "$av0 - internal redirect $h";
  978.                 print "temporary redirect taken $u *** $h -> " if $debug;
  979.                 $h = $temporary_redirect{"$u *** $htr"};
  980.                 print "$h\n" if $debug;
  981.                 next;
  982.             }
  983.         }
  984.         return $h;
  985.     }
  986. }
  987. # look up mx records with the name server.
  988. # re-queue expansion requests if possible
  989. # optionally give up on this host.
  990. sub mxlookup 
  991. {
  992.     local($lastchance,$server,$giveup,*users) = @_;
  993.     local(*T);
  994.     local(*NSLOOKUP);
  995.     local($nh, $pref,$cpref);
  996.     local($o0) = $0;
  997.     local($nserver);
  998.     local($name,$aliases,$type,$len,$thataddr);
  999.     local(%fallback);
  1000.  
  1001.     return 1 if &mxredirect($server,*users);
  1002.  
  1003.     if ((defined $mx{$server}) || (! $have_nslookup)) {
  1004.         return 0 unless $lastchance;
  1005.         &giveup('mx domainify',$giveup);
  1006.         return 0;
  1007.     }
  1008.  
  1009.     $0 = "$av0 - nslookup of $server";
  1010.     open(T,">/tmp/expn$$") || die "open > /tmp/expn$$: $!\n";
  1011.     print T "set querytype=MX\n";
  1012.     print T "$server\n";
  1013.     close(T);
  1014.     $cpref = 1.0E12;
  1015.     undef $nserver;
  1016.     open(NSLOOKUP,"nslookup < /tmp/expn$$ 2>&1 |") || die "open nslookup: $!";
  1017.     while(<NSLOOKUP>) {
  1018.         print if ($debug > 2);
  1019.         if (/mail exchanger = ([-A-Za-z_.0-9+]+)/) {
  1020.             $nh = $1;
  1021.             if (/preference = (\d+)/) {
  1022.                 $pref = $1;
  1023.                 if ($pref < $cpref) {
  1024.                     $nserver = $nh;
  1025.                     $cpref = $pref;
  1026.                 } elsif ($pref) {
  1027.                     $fallback{$pref} .= " $nh";
  1028.                 }
  1029.             }
  1030.         }
  1031.         if (/Non-existent domain/) {
  1032.             #
  1033.             # These addresss are hosed.  Kaput!  Dead! 
  1034.             # However, if we created the address in the
  1035.             # first place then there is a chance of 
  1036.             # salvation.
  1037.             #
  1038.             1 while(<NSLOOKUP>);    
  1039.             close(NSLOOKUP);
  1040.             return 0 unless $lastchance;
  1041.             &giveup('domainify',"$server: Non-existent domain",undef,1);
  1042.             return 0;    
  1043.         }
  1044.                 
  1045.     }
  1046.     close(NSLOOKUP);
  1047.     unlink("/tmp/expn$$");
  1048.     unless ($nserver) {
  1049.         $0 = "$o0 - finished mxlookup";
  1050.         return 0 unless $lastchance;
  1051.         &giveup('mx domainify',"$server: Could not resolve address");
  1052.         return 0;
  1053.     }
  1054.  
  1055.     # provide fallbacks in case $nserver doesn't work out
  1056.     if (defined $fallback{$cpref}) {
  1057.         $mx_secondary{$server} = $fallback{$cpref};
  1058.     }
  1059.  
  1060.     $0 = "$av0 - gethostbyname($nserver)";
  1061.     ($name,$aliases,$type,$len,$thataddr) = gethostbyname($nserver);
  1062.  
  1063.     unless ($thataddr) {
  1064.         $0 = $o0;
  1065.         return 0 unless $lastchance;
  1066.         &giveup('mx domainify',"$nserver: could not resolve address");
  1067.         return 0;
  1068.     }
  1069.     print "MX($server) = $nserver\n" if $debug;
  1070.     print "$server -> $nserver\n" if $vw && !$debug;
  1071.     $mx{&trhost($server)} = $nserver;
  1072.     # redeploy the users
  1073.     unless (&mxredirect($server,*users)) {
  1074.         return 0 unless $lastchance;
  1075.         &giveup('mx domainify',"$nserver: only one level of mx redirect allowed");
  1076.         return 0;
  1077.     }
  1078.     $0 = "$o0 - finished mxlookup";
  1079.     return 1;
  1080. }
  1081. # if mx expansion did not help to resolve an address
  1082. # (ie: foo@bar became @baz:foo@bar, then undo the 
  1083. # expansion).
  1084. # this is only used by &final
  1085. sub mxunroll
  1086. {
  1087.     local(*host,*addr) = @_;
  1088.     local($r) = 0;
  1089.     print "looking for mxbacktrace{$addr *** $host}\n"
  1090.         if ($debug > 1);
  1091.     while (defined $mxbacktrace{"$addr *** $host"}) {
  1092.         print "Unrolling MX expnasion: \@$host:$addr -> " 
  1093.             if ($debug || $verbose);
  1094.         $host = $mxbacktrace{"$addr *** $host"};
  1095.         print "\@$host:$addr\n" 
  1096.             if ($debug || $verbose);
  1097.         $r = 1;
  1098.     }
  1099.     return 1 if $r;
  1100.     $addr = "\@$host:$addr"
  1101.         if ($host =~ /\./);
  1102.     return 0;
  1103. }
  1104. # register a completed expnasion.  Make the final address as 
  1105. # simple as possible.
  1106. sub final
  1107. {
  1108.     local($addr,$host,$name,$error) = @_;
  1109.     local($he);
  1110.     local($hb,$hr);
  1111.     local($au,$ah);
  1112.  
  1113.     if ($error =~ /Non-existent domain/) {
  1114.         # 
  1115.         # If we created the domain, then let's undo the
  1116.         # damage...
  1117.         #
  1118.         if (defined $create_host_backtrack{"$addr *** $host"}) {
  1119.             while (defined $create_host_backtrack{"$addr *** $host"}) {
  1120.                 print "Un&domainifying($host) = " if $debug;
  1121.                 $host = $create_host_backtrack{"$addr *** $host"};
  1122.                 print "$host\n" if $debug;
  1123.             }
  1124.             $error = "$host: could not locate";
  1125.         } else {
  1126.             # 
  1127.             # If we only want valid addresses, toss out
  1128.             # bad host names.
  1129.             #
  1130.             if ($valid) {
  1131.                 print STDERR "\@$host:$addr ($name) Non-existent domain\n";
  1132.                 return "";
  1133.             }
  1134.         }
  1135.     }
  1136.  
  1137.     MXUNWIND: {
  1138.         $0 = "$av0 - final parsing of \@$host:$addr";
  1139.         ($he = $host) =~ s/(\W)/\\$1/g;
  1140.         if ($addr !~ /@/) {
  1141.             # addr does not contain any host
  1142.             $addr = "$addr@$host";
  1143.         } elsif ($addr !~ /$he/i) {
  1144.             # if host part really something else, use the something
  1145.             # else.
  1146.             if ($addr =~ m/(.*)\@([^\@]+)$/) {
  1147.                 ($au,$ah) = ($1,$2);
  1148.                 print "au = $au ah = $ah\n" if $debug;
  1149.                 if (defined $temporary_redirect{"$addr *** $ah"}) {
  1150.                     $addr = "$au\@".$temporary_redirect{"$addr *** $ah"};
  1151.                     print "Rewrite! to $addr\n" if $debug;
  1152.                     next MXUNWIND;
  1153.                 }
  1154.             }
  1155.             # addr does not contain full host
  1156.             if ($valid) {
  1157.                 if ($host =~ /^([^\.]+)(\..+)$/) {
  1158.                     # host part has a . in it - foo.bar
  1159.                     ($hb, $hr) = ($1, $2);
  1160.                     if ($addr =~ /\@([^\.\@]+)$/ && ($1 eq $hb)) {
  1161.                         # addr part has not . 
  1162.                         # and matches beginning of
  1163.                         # host part -- tack on a 
  1164.                         # domain name.
  1165.                         $addr .= $hr;
  1166.                     } else {
  1167.                         &mxunroll(*host,*addr) 
  1168.                             && redo MXUNWIND;
  1169.                     }
  1170.                 } else {
  1171.                     &mxunroll(*host,*addr) 
  1172.                         && redo MXUNWIND;
  1173.                 }
  1174.             } else {
  1175.                 $addr = "${addr}[\@$host]"
  1176.                     if ($host =~ /\./);
  1177.             }
  1178.         }
  1179.     }
  1180.     $name = "$name " if $name;
  1181.     $error = " $error" if $error;
  1182.     if ($valid) {
  1183.         push(@final,"$name<$addr>");
  1184.     } else {
  1185.         push(@final,"$name<$addr>$error");
  1186.     }
  1187.     "\t$name<$addr>$error\n";
  1188. }
  1189.  
  1190. sub alarm
  1191. {
  1192.     local($alarm_action,$alarm_redirect,$alarm_user) = @_;
  1193.     alarm(3600);
  1194.     $SIG{ALRM} = 'handle_alarm';
  1195. }
  1196. # this involves one great big ugly hack.
  1197. # the "next HOST" unwinds the stack!
  1198. sub handle_alarm
  1199. {
  1200.     &giveup($alarm_redirect,"Timed out during $alarm_action",$alarm_user);
  1201.     next HOST;
  1202. }
  1203.  
  1204. # read the rest of the current smtp daemon's response (and toss it away)
  1205. sub read_response
  1206. {
  1207.     local($done,$watch) = @_;
  1208.     local(@resp);
  1209.     print $s if $watch;
  1210.     while(($done eq "-") && ($s = <S>) && ($s =~ /^\d+([- ])/)) {
  1211.         print $s if $watch;
  1212.         $done = $1;
  1213.         push(@resp,$s);
  1214.     }
  1215.     return @resp;
  1216. }
  1217. # print args if verbose.  Return them in any case
  1218. sub verbose
  1219. {
  1220.     local(@tp) = @_;
  1221.     print "@tp" if $verbose;
  1222. }
  1223. # to pass perl -w:
  1224. @tp;
  1225. $flag_a;
  1226. $flag_d;
  1227. $flag_1;
  1228. %already_domainify_fellback;
  1229. %already_mx_fellback;
  1230. &handle_alarm;
  1231. ################### BEGIN PERL/TROFF TRANSITION 
  1232. .00 ;    
  1233.  
  1234. 'di
  1235. .nr nl 0-1
  1236. .nr % 0
  1237. .\\"'; __END__ 
  1238. .\" ############## END PERL/TROFF TRANSITION
  1239. .TH EXPN 1 "March 11, 1993"
  1240. .AT 3
  1241. .SH NAME
  1242. expn \- recursively expand mail aliases
  1243. .SH SYNOPSIS
  1244. .B expn
  1245. .RI [ -a ]
  1246. .RI [ -v ]
  1247. .RI [ -w ]
  1248. .RI [ -d ]
  1249. .RI [ -1 ]
  1250. .IR user [@ hostname ]
  1251. .RI [ user [@ hostname ]]...
  1252. .SH DESCRIPTION
  1253. .B expn
  1254. will use the SMTP
  1255. .B expn
  1256. and 
  1257. .B vrfy
  1258. commands to expand mail aliases.  
  1259. It will first look up the addresses you provide on the command line.
  1260. If those expand into addresses on other systems, it will 
  1261. connect to the other systems and expand again.  It will keep 
  1262. doing this until no further expansion is possible.
  1263. .SH OPTIONS
  1264. The default output of 
  1265. .B expn
  1266. can contain many lines which are not valid
  1267. email addresses.  With the 
  1268. .I -aa
  1269. flag, only expansions that result in legal addresses
  1270. are used.  Since many mailing lists have an illegal
  1271. address or two, the single
  1272. .IR -a ,
  1273. address, flag specifies that a few illegal addresses can
  1274. be mixed into the results.   More 
  1275. .I -a
  1276. flags vary the ratio.  Read the source to track down
  1277. the formula.  With the
  1278. .I -a
  1279. option, you should be able to construct a new mailing
  1280. list out of an existing one.
  1281. .LP
  1282. If you wish to limit the number of levels deep that 
  1283. .B expn
  1284. will recurse as it traces addresses, use the
  1285. .I -1
  1286. option.  For each 
  1287. .I -1
  1288. another level will be traversed.  So, 
  1289. .I -111
  1290. will traverse no more than three levels deep.
  1291. .LP
  1292. The normal mode of operation for
  1293. .B expn
  1294. is to do all of its work silently.
  1295. The following options make it more verbose.
  1296. It is not necessary to make it verbose to see what it is
  1297. doing because as it works, it changes its 
  1298. .BR argv [0]
  1299. variable to reflect its current activity.
  1300. To see how it is expanding things, the 
  1301. .IR -v ,
  1302. verbose, flag will cause 
  1303. .B expn 
  1304. to show each address before
  1305. and after translation as it works.
  1306. The 
  1307. .IR -w ,
  1308. watch, flag will cause
  1309. .B expn
  1310. to show you its conversations with the mail daemons.
  1311. Finally, the 
  1312. .IR -d ,
  1313. debug, flag will expose many of the inner workings so that
  1314. it is possible to eliminate bugs.
  1315. .SH ENVIRONMENT
  1316. No enviroment variables are used.
  1317. .SH FILES
  1318. .PD 0
  1319. .B /tmp/expn$$
  1320. .B temporary file used as input to 
  1321. .BR nslookup .
  1322. .SH SEE ALSO
  1323. .BR aliases (5), 
  1324. .BR sendmail (8),
  1325. .BR nslookup (8),
  1326. RFC 823, and RFC 1123.
  1327. .SH BUGS
  1328. Not all mail daemons will implement 
  1329. .B expn
  1330. or
  1331. .BR vrfy .
  1332. It is not possible to verify addresses that are served
  1333. by such daemons.
  1334. .LP
  1335. When attempting to connect to a system to verify an address,
  1336. .B expn
  1337. only tries one IP address.  Most mail daemons
  1338. will try harder.
  1339. .LP
  1340. It is assumed that you are running domain names and that 
  1341. the 
  1342. .BR nslookup (8) 
  1343. program is available.  If not, 
  1344. .B expn
  1345. will not be able to verify many addresses.  It will also pause
  1346. for a long time unless you change the code where it says
  1347. .I $have_nslookup = 1
  1348. to read
  1349. .I $have_nslookup = 
  1350. .IR 0 .
  1351. .LP
  1352. Lastly, 
  1353. .B expn
  1354. does not handle every valid address.  If you have an example,
  1355. please submit a bug report.
  1356. .SH CREDITS
  1357. In 1986 or so, Jon Broome wrote a program of the same name
  1358. that did about the same thing.  It has since suffered bit rot
  1359. and Jon Broome has dropped off the face of the earth!
  1360. (Jon, if you are out there, drop me a line)
  1361. .SH AVAILABILITY
  1362. The latest version of 
  1363. .B expn
  1364. is available through anonymous ftp at
  1365. .IR ftp://ftp.idiom.com/pub/muir-programs/expn .
  1366. .SH AUTHOR
  1367. .I David Muir Sharnoff\ \ \ \ <muir@idiom.com>
  1368.